package com.yingsheng.bi.hks.web.component;

import com.yingsheng.bi.hks.basic.exception.ExternalRuntimeException;
import com.yingsheng.bi.hks.basic.exception.FeignRuntimeException;
import com.yingsheng.bi.hks.basic.exception.ServiceRuntimeException;
import com.yingsheng.bi.hks.dto.HksRes;
import io.swagger.annotations.ApiModelProperty;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.StringUtils;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 异常处理
 *
 * @author root
 */
@RestControllerAdvice
public class GlobalExceptionHandler extends AbstractExceptionHandler {

    private boolean swagger = true;

    @Value("${spring.application.name}")
    private String applicationName;

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public HksRes httpMessageNotReadableExceptionHandle(HttpMessageNotReadableException e) {
        log.error(e.getMessage(), e);
        HksRes exceptionRes = response();
        exceptionRes.setReturnCode("000001");
        exceptionRes.setReturnMsg("请求数据异常");
        String message = e.getMessage();
        if (message.startsWith("Required request body is missing")) {
            exceptionRes.setErrorCode("request.body.is.missing");
        } else if (message.startsWith("JSON parse error")) {
            exceptionRes.setErrorCode("json.parse.error");
        } else {
            exceptionRes.setErrorCode("request.data.error");
        }
        exceptionRes.setErrorMsg(message);
        return exceptionRes;
    }

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    public HksRes methodArgumentTypeMismatchExceptionHandle(MethodArgumentTypeMismatchException e) {
        log.error(e.getMessage(), e);
        HksRes exceptionRes = response();
        exceptionRes.setReturnCode("000002");
        exceptionRes.setReturnMsg("方法参数类型不匹配");
        exceptionRes.setErrorCode("argument.type.mismatch");
        exceptionRes.setErrorMsg(e.getMessage());
        return exceptionRes;
    }

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public HksRes missingServletRequestParameterExceptionHandle(MissingServletRequestParameterException e) {
        log.error(e.getMessage(), e);
        String parameterName = e.getParameterName();
        String parameterNameMsg = getExceptionMessage(parameterName);
        HksRes exceptionRes = response();
        exceptionRes.setReturnCode("000003");
        exceptionRes.setReturnMsg("请求参数缺失");
        exceptionRes.setErrorCode("param is missing [" + parameterName + "]");
        exceptionRes.setErrorMsg("参数缺失:" + parameterNameMsg);
        return exceptionRes;
    }

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public HksRes methodArgumentNotValidExceptionHandle(MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        List<ObjectError> bindingErrors = e.getBindingResult().getAllErrors();
        List<String> errorCodeList = new ArrayList<>(bindingErrors.size());
        List<String> errorMessageList = new ArrayList<>(bindingErrors.size());
        for (ObjectError bindingError : bindingErrors) {
            String errorCode = bindingError.getCodes()[0];
            String errorMessage = bindingError.getDefaultMessage();
            String name = bindingError.getObjectName();
            Object value = "";
            if (bindingError instanceof FieldError) {
                FieldError fieldError = (FieldError) bindingError;
                name = fieldError.getField();
                value = fieldError.getRejectedValue();
            }
            ConstraintViolation violation = bindingError.unwrap(ConstraintViolation.class);
            String messageTemplate = violation.getMessageTemplate();
            if (StringUtils.hasText(messageTemplate) && messageTemplate.startsWith("{javax.validation")) {
                String[] codes = bindingError.getCodes();
                for (int i = 0; i < codes.length - 2; i++) {
                    String code = codes[i];
                    String codeMessage = getExceptionMessage(code);
                    if (!code.equals(codeMessage)) {
                        errorMessage = codeMessage;
                        break;
                    } else {
                        String apiModelPropertyName = null;
                        if (swagger) {
                            Path path = violation.getPropertyPath();
                            if (path instanceof PathImpl) {
                                String propertyName = ((PathImpl) path).getLeafNode().getName();
                                Class clazz = violation.getLeafBean().getClass();
                                Field field = null;
                                do {
                                    try {
                                        field = clazz.getDeclaredField(propertyName);
                                        ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
                                        if (apiModelProperty != null) {
                                            apiModelPropertyName = apiModelProperty.value();
                                            if (!StringUtils.hasText(apiModelPropertyName)) {
                                                apiModelPropertyName = apiModelProperty.name();
                                            }
                                        }
                                    } catch (Exception e1) {
                                    }
                                } while (field == null && (clazz = clazz.getSuperclass()) != Object.class);

                            }
                        }
                        if (!StringUtils.hasText(apiModelPropertyName)) {
                            int index = code.indexOf(46);
                            if (index > -1) {
                                code = code.substring(index + 1);
                                codeMessage = getExceptionMessage(code);
                                if (!code.equals(codeMessage)) {
                                    errorMessage = codeMessage + errorMessage;
                                    break;
                                }
                            }
                        } else {
                            errorMessage = apiModelPropertyName + errorMessage;
                            break;
                        }
                    }
                }
            }
            errorCodeList.add(errorCode);
            errorMessageList.add(errorMessage);
            log.info("{},{},{},{}", name, value, errorCode, errorMessage);
        }
        String resErrorCode = errorCodeList.stream().collect(Collectors.joining(","));
        String resErrorMessage = errorMessageList.stream().collect(Collectors.joining(","));
        HksRes exceptionRes = response();
        exceptionRes.setReturnCode("000005");
        exceptionRes.setReturnMsg("请求参数校验异常");
        exceptionRes.setErrorCode(resErrorCode);
        exceptionRes.setErrorMsg(resErrorMessage);
        return exceptionRes;
    }

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = IllegalArgumentException.class)
    public HksRes illegalArgumentExceptionHandle(IllegalArgumentException e) {
        log.error(e.getMessage(), e);
        HksRes exceptionRes = response();
        exceptionRes.setReturnCode("100001");
        exceptionRes.setReturnMsg("业务数据非法");
        exceptionRes.setErrorCode(e.getMessage());
        exceptionRes.setErrorMsg(getExceptionMessage(e.getMessage()));
        return exceptionRes;
    }

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = ConstraintViolationException.class)
    public HksRes constraintViolationExceptionHandle(ConstraintViolationException e) {
        log.error(e.getMessage(), e);
        HksRes exceptionRes = response();
        exceptionRes.setReturnCode("100002");
        exceptionRes.setReturnMsg("业务方法参数校验异常");
        exceptionRes.setErrorCode(e.getMessage());
        exceptionRes.setErrorMsg(e.getMessage());
        return exceptionRes;
    }

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = ServiceRuntimeException.class)
    public HksRes serviceRuntimeExceptionHandle(ServiceRuntimeException e) {
        log.error(e.getMessage(), e);
        HksRes exceptionRes = response();
        exceptionRes.setReturnCode("199999");
        exceptionRes.setReturnMsg("业务处理异常");
        exceptionRes.setErrorCode(e.getMessage());
        exceptionRes.setErrorMsg(getExceptionMessage(e.getMessage(), e.getArgs()));
        return exceptionRes;
    }

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = FeignRuntimeException.class)
    public HksRes feignRuntimeExceptionHandle(FeignRuntimeException e) {
        log.error("{} 响应异常, 具体信息如下", e.getSource());
        log.error(e.getMessage(), e);
        HksRes exceptionRes = response();
        exceptionRes.addSource(e.getSource());
        if (StringUtils.hasText(e.getReturnCode())) {
            exceptionRes.setReturnCode(e.getReturnCode());
            exceptionRes.setReturnMsg(e.getReturnMsg());
        } else {
            exceptionRes.setReturnCode("200001");
            exceptionRes.setReturnMsg("Feign调用异常");
        }
        exceptionRes.setErrorCode(e.getErrorCode());
        exceptionRes.setErrorMsg(e.getMessage());
        return exceptionRes;
    }

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = ExternalRuntimeException.class)
    public HksRes externalRuntimeExceptionHandle(ExternalRuntimeException e) {
        log.error("{} 响应异常, 具体信息如下", e.getSource());
        log.error(e.getMessage(), e);
        HksRes exceptionRes = response();
        exceptionRes.addSource(e.getSource());
        exceptionRes.setReturnCode("299999");
        exceptionRes.setReturnMsg("外部调用异常");
        exceptionRes.setErrorCode(e.getErrorCode());
        exceptionRes.setErrorMsg(e.getMessage());
        return exceptionRes;
    }

    @ResponseStatus(value = HttpStatus.OK)
    @ExceptionHandler(value = Exception.class)
    public HksRes exceptionHandle(Exception e) {
        log.error(e.getMessage(), e);
        HksRes exceptionRes = response();
        exceptionRes.setReturnCode("999999");
        exceptionRes.setReturnMsg("系统服务异常,请稍后再试!");
        exceptionRes.setErrorCode("system.error");
        exceptionRes.setErrorMsg("系统服务异常,请稍后再试!");
        return exceptionRes;
    }

    private HksRes response() {
        HksRes res = new HksRes();
        res.setSource(this.applicationName);
        return res;
    }

}
